<?php

// 这是跨应用的公共文件

if (!function_exists('debug_log')) {
    /**
     * 将变量结果写入日志文件，方便调试，调试完必须删除该函数的调用，否则后续开发者会因看到太多信息而无法调试
     */
    function debug_log()
    {
        ob_start();
        foreach (func_get_args() as $arg) {
            var_dump($arg);
        }
        $info = ob_get_clean();
        if (empty($info)) {
            return;
        }
        $date = date('Y-m-d H:i:s');
        $mesage = "[$date]\r\n$info\r\n\r\n";
        error_log($mesage, 3, runtime_path() . 'debug.log');
    }
}

if (!function_exists('success')) {
    /**
     * 将成功的结果数组以异常的形式返回
     */
    function success($data = null)
    {
        throw new app\common\JsonException(0, 'success', $data);
    }
}

if (!function_exists('failure')) {
    /**
     * 将逻辑错误的结果数组以异常的形式返回
     */
    function failure($code = 1, $msg = null, $data = null)
    {
        throw new app\common\JsonException($code, $msg, $data);
    }
}

if (!function_exists('settings')) {
    /**
     * 获取或保存设置项
     * @param mixed $name 设置项名称，多个获取时，名称用逗号连接成字符串；多个保存时，名称与值组成键值对的数组作为第一参数，忽略第二参数
     * @param mixed $value 设置项值
     * @return mixed
     */
    function settings($name = '', $value = null)
    {
        if (is_string($name) && !isset($value)) {
            return app('app\service\SettingsService')->getSettings($name);
        } else {
            return app('app\service\SettingsService')->saveSettings($name, $value);
        }
    }
}

if (!function_exists('redis_handle')) {
    /**
     * 获取redis访问句柄
     * @param int $dbIndex 当前数据库编号
     * @param string $config 配置key
     * @param bool $persistent 是否长连接
     * @return Redis
     */
    function redis_handle(int $dbIndex = 0, string $config = 'default', bool $persistent = false)
    {
        $hostname = config('redis.' . $config . '.hostname');
        $hostport = config('redis.' . $config . '.hostport');
        $password = config('redis.' . $config . '.password');
        $redis = new \Redis();
        if ($persistent) {
            $redis->connect($hostname, $hostport);
        } else {
            $redis->pconnect($hostname, $hostport);
        }
        if (!empty($password)) {
            $redis->auth($password);
        }
        $redis->select($dbIndex);
        return $redis;
    }
}

if (!function_exists('rand_str')) {
    /**
     * 生成随机字符串
     * @param int $length 字符串长度
     * @param array $type 逐个字符定义随机字符集，索引数组，索引0表示默认字符集，索引i(i>0)表示第i个字符的随机字符集，字符集由基础字符集组合，对应于数组值的某个二进制位
     * @param array $codeSet 重定义内部基础字符集
     * @return string
     */
    function rand_str(int $length = 6, array $type = [], array $codeSet = [])
    {
        $set = [
            'number' => '0123456789',
            'lower' => 'abcdefghijklmnopqrstuvwxyz',
            'upper' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
            'special' => '~!@#$%^&*()_+{}:\"|<>?`-=[];\'\\,./',
            'hex' => '0123456789abcdef',
            'other' => '',
        ];
        $set = array_merge($set, $codeSet);
        $combinedSet = [];
        foreach ($type as $k => $t) {
            $t = (int) $t;
            $combinedSet[$k] = '';
            if (($t & 1) > 0) {
                $combinedSet[$k] .= $set['number'];
            }
            if (($t & 2) > 0) {
                $combinedSet[$k] .= $set['lower'];
            }
            if (($t & 4) > 0) {
                $combinedSet[$k] .= $set['upper'];
            }
            if (($t & 8) > 0) {
                $combinedSet[$k] .= $set['special'];
            }
            if (($t & 16) > 0) {
                $combinedSet[$k] .= $set['hex'];
            }
            if (($t & 32) > 0) {
                $combinedSet[$k] .= $set['other'];
            }
        }
        if (!isset($combinedSet[0]) || $combinedSet[0] == '') {
            $combinedSet[0] = '0123456789';
        }
        $result = '';
        for ($i = 1; $i <= $length; $i++) {
            $charSet = (!isset($combinedSet[$i]) || $combinedSet[$i] == '') ? $combinedSet[0] : $combinedSet[$i];
            $result .= $charSet[mt_rand(0, strlen($charSet) - 1)];
        }
        return $result;
    }
}

if (!function_exists('rand_include')) {
    /**
     * 必须包含类型的随机
     * @return array
     */
    function rand_include(array $flag, int $len, int $default = null)
    {
        $flagCount = count($flag);
        if ($len < $flagCount) {
            $len = $flagCount;
        }
        $temp = range(0, $len - 1);
        $holdBits = 0;
        $result = empty($default) ? array_fill(0, $len, 0) : array_fill(0, $len + 1, $default);
        foreach ($flag as $i => $f) {
            if (is_string($f)) {
                [$f, $bits] = explode('|', $f);
                $bits = (int) $bits;
            } else {
                //最大可用位数 = 字符串长度 - 最少预留位数 - 已使用位数
                $maxBits = $len - ($flagCount - $i - 1) - $holdBits;
                $bits = mt_rand(1, $maxBits);
            }
            $holdBits += $bits;
            for ($j = 0; $j < $bits; $j++) {
                $place = mt_rand(0, count($temp) - 1);
                $result[$temp[$place] + (empty($default) ? 0 : 1)] = (int) $f;
                unset($temp[$place]);
                if (empty($temp)) {
                    break 2;
                }
                $temp = array_values($temp);
            }
        }
        return $result;
    }
}

if (!function_exists('is_https')) {
    /**
     * 当前请求类型是否为https
     * @return bool
     */
    function is_https(bool $only_host = true)
    {
        return ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'));
    }
}

if (!function_exists('site_url')) {
    /**
     * 通过设置中的域名构造URL，以便在swoole服务器等非http请求中正确返回结果
     * @return string
     */
    function site_url($uri = '')
    {
        $protocol = config('app.server_protocol');
        $domain = config('app.server_domain');
        $port = config('app.server_port');
        if (is_null($uri)) {
            return $domain;
        }
        $portStr = ':' . $port;
        if ($port == '80' || $port == '443') {
            $portStr = '';
        }
        return $protocol . '://' . $domain . $portStr . '/' . ltrim($uri, '/');
    }
}

if (!function_exists('sign_data')) {
    /**
     * 对数组进行签名
     * @param array $data 准备签名的数据
     * @param string $secret 签名密钥
     * @param bool $append 是否将签名结果追加到准备签名的数据并返回，如果否，则只返回签名结果
     * @param bool $extra 是否添加额外信息：时间戳、随机数
     * @return array|string
     */
    function sign_data(array $data, string $secret, bool $append = true, bool $extra = true)
    {
        if ($extra) {
            $data['timestamp'] = time();
            $data['nonce'] = mt_rand();
        }
        ksort($data);
        $sign = sha1("sign_secret={$secret}&" . http_build_query($data));
        if ($append) {
            $data['sign'] = $sign;
            return $data;
        } else {
            return $sign;
        }
    }
}

if (!function_exists('sign_clear')) {
    /**
     * 清除数组中的签名相关字段
     * @param array $data 要删除字段的数组
     * @return array
     */
    function sign_clear(array &$data)
    {
        unset($data['sign']);
        unset($data['timestamp']);
        unset($data['nonce']);
    }
}

if (!function_exists('verify_sign')) {
    /**
     * 签名验证
     * @param array $data 签名的数据，需包含签名结果
     * @param string $secret 签名密钥
     * @return bool
     */
    function verify_sign(array $data, string $secret)
    {
        if (!isset($data['sign'])) {
            return false;
        }
        $sign = strtolower($data['sign']);
        unset($data['sign']);
        return strcmp(sign_data($data, $secret, false, false), $sign) === 0;
    }
}

if (!function_exists('verify_timestamp')) {
    /**
     * 验证时间戳
     * @param array $data 带时间戳的参数
     * @param int $expires 时间戳有效期，单位：秒
     * @return bool
     */
    function verify_timestamp(array $data, int $expires = 0)
    {
        if (!isset($data['timestamp'])) {
            return false;
        }
        $timestamp = $data['timestamp'];
        if (!is_scalar($timestamp) || $timestamp > 10000000000) {
            return false;
        }
        if (time() - (int) $timestamp > $expires) {
            return false;
        }
        return true;
    }
}

if (!function_exists('generate_id')) {
    /**
     * 通过mysql产生自增id并返回
     * @param string $table_flag 数据表标识
     * @return string
     */
    function generate_id(string $table_flag)
    {
        return app('db')->name($table_flag)->replace()->insertGetId([
            'stage' => '',
        ]);
    }
}

if (!function_exists('generate_wechatapi_token')) {
    /**
     * 生成微信消息处理接口token
     */
    function generate_wechatapi_token()
    {
        $len = 10;
        $type = rand_include([1, 2, 4], $len - 1);
        array_unshift($type, (1 | 2 | 4), 2);
        return rand_str($len, $type);
    }
}

if (!function_exists('mask_str')) {
    /**
     * 对字符串打上星号掩码
     */
    function mask_str(&$str, $headLen = 4, $tailLen = 4)
    {
        if (strlen($str) > $headLen + $tailLen) {
            $str = substr($str, 0, $headLen) . '****' . substr($str, -$tailLen, $tailLen);
        }
    }
}

if (!function_exists('make_auth_url')) {
    /**
     * 生成授权跳转链接
     */
    function make_auth_url(string $from_url, string $logic_url = null, array $data = [])
    {
        if (empty($logic_url)) {
            $logic_url = (string) url('wechat/OfficialAccount/login')->suffix(false)->domain(true);
        }
        $data = array_merge($data, [
            'from_url' => $from_url,
            'logic_url' => $logic_url,
        ]);
        $data = sign_data($data, config('app.officialaccount_auth_secret'));
        $url = (string) url('wechat/OfficialAccount/authRedirect')->suffix(false)->domain(true);
        $url .= '?' . http_build_query($data);
        return $url;
    }
}

if (!function_exists('push_message')) {
    /**
     * 向自定义服务器投递消息
     */
    function push_message($server, $msgAction, $msgData, $clientInfo = null)
    {
        try {
            $data = [
                'msgAction' => $msgAction,
                'msgData' => $msgData,
                'clientInfo' => $clientInfo,
            ];
            $data = sign_data($data, config('app.' . $server . '.secret'));
            $data = json_encode($data, JSON_UNESCAPED_UNICODE);
            if (config('app.' . $server . '.msg_engine') == 'http_server') {
                $options = [
                    'http' => [
                        'method' => 'POST',
                        'header' => 'Content-type:application/json;charset=utf-8',
                        'content' => $data,
                        'timeout' => 5,
                    ],
                    'ssl' => [
                        "verify_peer" => false,
                        "verify_peer_name" => false,
                    ],
                ];
                $protocol = empty(config('app.' . $server . '.enable_ssl')) ? 'http' : 'https';
                $host = config('app.' . $server . '.host');
                $host == '0.0.0.0' && $host = '127.0.0.1';
                $port = config('app.' . $server . '.port');
                $url = "$protocol://$host:$port";
                $contents = file_get_contents($url, false, stream_context_create($options));
                $result = json_decode($contents, true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    throw new \Exception("error: " . json_last_error_msg() . PHP_EOL . "content: $contents");
                }
                if (!isset($result['code']) || $result['code'] !== 0) {
                    throw new \Exception($contents);
                }
            }
            if (config('app.' . $server . '.msg_engine') == 'redis_subscribe') {
                $redis = redis_handle();
                $result = $redis->publish("msg_{$server}", $data);
                $redis->close();
                if ($result <= 0) {
                    throw new \Exception('The server appears to have downtime');
                }
            }
        } catch (\Exception $ex) {
            $errorRecord = '[' . date('Y-m-d H:i:s') . ']' . PHP_EOL;
            $errorRecord .= $ex->getMessage() . PHP_EOL . PHP_EOL;
            $logFile = root_path() . 'runtime' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . "push_message_{$server}.log";
            error_log($errorRecord, 3, $logFile);
            return false;
        }
        return true;
    }
}
